package com.ruoyi.framework.web.service.app;

import com.ruoyi.common.constant.Constants;
import com.ruoyi.common.constant.UserConstants;
import com.ruoyi.common.core.domain.entity.H5User;
import com.ruoyi.common.core.domain.model.H5LoginBody;
import com.ruoyi.common.core.domain.model.H5LoginUser;
import com.ruoyi.common.exception.ServiceException;
import com.ruoyi.common.utils.DateUtils;
import com.ruoyi.common.utils.MessageUtils;
import com.ruoyi.common.utils.StringUtils;
import com.ruoyi.common.utils.ip.IpUtils;
import com.ruoyi.common.utils.SecurityUtils;
import com.ruoyi.framework.manager.AsyncManager;
import com.ruoyi.framework.manager.factory.AsyncFactory;
import com.ruoyi.framework.security.SmsAuthenticationToken;
import com.ruoyi.framework.security.context.AuthenticationContextHolder;
import com.ruoyi.system.sms.SmsCodeService;
import com.ruoyi.framework.util.LoginCommonUtil;
import com.ruoyi.framework.web.service.TokenService;
import com.ruoyi.system.service.IH5UserService;
import com.ruoyi.system.service.ISysConfigService;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.security.authentication.*;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;

/**
 * H5用户登录服务接口
 * 处理H5端用户的认证、注册、退出等业务逻辑
 *
 * @author ruoyi
 */
@Service
public class H5LoginService {


    @Autowired
    @Qualifier("h5AuthenticationManager")
    private AuthenticationManager h5AuthenticationManager;

    @Autowired
    private TokenService tokenService;

    @Autowired
    private IH5UserService h5UserService;

    @Autowired
    private ISysConfigService configService;

    @Autowired
    private LoginCommonUtil loginCommonUtil;

    @Autowired
    private SmsCodeService smsCodeService;

    @Value("${aliyun.sms.enabled:false}")
    private boolean smsEnabled;

    @Value("${aliyun.sms.mock.enabled:false}")
    private boolean mockEnabled;

    @Resource
    private AuthenticationManager authenticationManager;

    /**
     * H5用户登录逻辑（支持密码登录和短信验证码登录）
     */
    public String login(H5LoginBody loginBody) {
        // 1. 参数校验
        validateLoginParams(loginBody);

        Authentication authentication;
        // 用于日志的用户名/手机号
        String username = "sms".equals(loginBody.getLoginType()) ? loginBody.getPhoneNumber() : loginBody.getUsername();
//        loginCommonUtil.validateLoginCaptcha(loginBody.getPhoneNumber(), loginBody.getCode(), loginBody.getUuid());
        try {
            // 2. 根据登录类型进行不同的认证
            if ("sms".equals(loginBody.getLoginType())) {
                // 短信登录逻辑修改
                if (!smsEnabled && !mockEnabled) {
                    throw new ServiceException("短信登录功能暂未开放");
                }
                // 验证验证码（原有逻辑不变）
                if (!smsCodeService.verifySmsCode(loginBody.getPhoneNumber(), loginBody.getSmsCode(), "login")) {
                    throw new ServiceException("短信验证码错误或已过期");
                }

                // 关键修改：用短信专用令牌，仅传手机号
                SmsAuthenticationToken smsToken = new SmsAuthenticationToken(loginBody.getPhoneNumber());
                AuthenticationContextHolder.setContext(smsToken);
                authentication = h5AuthenticationManager.authenticate(smsToken);
            } else {
                // 密码登录逻辑不变
                UsernamePasswordAuthenticationToken passwordToken = new UsernamePasswordAuthenticationToken(
                        loginBody.getUsername(), loginBody.getPassword());
                AuthenticationContextHolder.setContext(passwordToken);
                authentication = h5AuthenticationManager.authenticate(passwordToken);
            }
            // 3. 认证成功：记录日志
            H5LoginUser loginUser = (H5LoginUser) authentication.getPrincipal();
            AsyncManager.me().execute(AsyncFactory.recordLogininfor(loginUser.getUsername(), Constants.LOGIN_SUCCESS, "h5.user.login.success"));

            // 4. 更新登录信息（IP、时间、设备）
            recordLoginInfo(loginUser.getUserId());

            // 5. 生成JWT令牌并返回
            return tokenService.createToken(loginUser);

        } catch (AuthenticationException e) {
            // 6. 认证失败：记录日志并抛异常
            String errorMsg;
            if (e instanceof BadCredentialsException) {
                errorMsg = "验证码错误或密码不正确";
            } else if (e instanceof DisabledException) {
                errorMsg = "用户已被禁用，请联系管理员";
            } else if (e instanceof AccountExpiredException) {
                errorMsg = "账号已过期";
            } else if (e instanceof LockedException) {
                errorMsg = "账号已被锁定";
            } else {
                errorMsg = "登录失败：" + e.getMessage();
            }
            AsyncManager.me().execute(AsyncFactory.recordLogininfor(username, Constants.LOGIN_FAIL, errorMsg));
            throw new ServiceException(errorMsg);
        } finally {
            // 7. 无论成功失败，强制清除线程临时信息
            AuthenticationContextHolder.clearContext();
        }
    }

    /**
     * 验证登录参数
     */
    private void validateLoginParams(H5LoginBody loginBody) {

        if ("password".equals(loginBody.getLoginType())) {
            if (StringUtils.isEmpty(loginBody.getUsername())) {
                throw new ServiceException("用户名不能为空");
            }
            if (StringUtils.isEmpty(loginBody.getPassword())) {
                throw new ServiceException("密码不能为空");
            }
            // 密码登录的前置校验
            loginPreCheck(loginBody.getUsername(), loginBody.getPassword());
        } else if ("sms".equals(loginBody.getLoginType())) {
            if (StringUtils.isEmpty(loginBody.getSmsCode())) {
                throw new ServiceException("短信验证码不能为空");
            }
        } else {
            throw new ServiceException("不支持的登录类型");
        }
    }

    /**
     * H5用户退出逻辑
     */
    public void logout(HttpServletRequest request, HttpServletResponse response) {
        // 1. 从请求头获取token
        String token = tokenService.getToken(request);
        if (StringUtils.isNotEmpty(token)) {
            // 2. 解析token获取用户名
            String username = tokenService.getUsernameFromToken(token);
            // 3. 移除Redis中的token缓存
            tokenService.delH5LoginUser(token);
            // 4. 记录退出日志
            AsyncManager.me().execute(AsyncFactory.recordLogininfor(username, Constants.LOGOUT,
                    MessageUtils.message("h5.user.logout.success")));
        }
    }

    /**
     * 获取当前登录H5用户信息
     */
    public Object getLoginUserInfo() {
        // 1. 从Security上下文获取当前登录用户
        H5LoginUser loginUser = SecurityUtils.getH5LoginUser();
        tokenService.refreshToken(loginUser);
        // 2. 构建返回信息（过滤敏感字段）
        Map<String, Object> userInfo = new HashMap<>(8);
        userInfo.put("userId", loginUser.getUserId());
        userInfo.put("userName", loginUser.getUsername());
        userInfo.put("nickName", loginUser.getH5User().getNickName());
        userInfo.put("phonenumber", loginUser.getH5User().getPhonenumber());
        userInfo.put("avatar", loginUser.getH5User().getAvatar());
        userInfo.put("userType", loginUser.getH5User().getUserType());
        userInfo.put("roles", loginUser.getAuthorities().stream()
                .map(GrantedAuthority::getAuthority)
                .toArray()); // 角色列表
        return userInfo;
    }

    /**
     * 登录前置校验
     */
    private void loginPreCheck(String username, String password) {
        // 1. 用户名/密码为空校验
        if (StringUtils.isEmpty(username) || StringUtils.isEmpty(password)) {
            AsyncManager.me().execute(AsyncFactory.recordLogininfor(username, Constants.LOGIN_FAIL,
                    MessageUtils.message("not.null")));
            throw new ServiceException("用户名或密码不能为空");
        }

        // 2. 密码长度校验
        if (password.length() < UserConstants.PASSWORD_MIN_LENGTH || password.length() > UserConstants.PASSWORD_MAX_LENGTH) {
            AsyncManager.me().execute(AsyncFactory.recordLogininfor(username, Constants.LOGIN_FAIL,
                    MessageUtils.message("h5.user.password.length")));
            throw new ServiceException("密码长度必须在" + UserConstants.PASSWORD_MIN_LENGTH + "-"
                    + UserConstants.PASSWORD_MAX_LENGTH + "位之间");
        }

        // 3. IP黑名单校验
        String blackIpList = configService.selectConfigByKey("h5.login.blackIPList");
        if (IpUtils.isMatchedIp(blackIpList, IpUtils.getIpAddr())) {
            AsyncManager.me().execute(AsyncFactory.recordLogininfor(username, Constants.LOGIN_FAIL,
                    MessageUtils.message("login.blocked")));
            throw new ServiceException("当前IP已被禁止登录");
        }
    }

    /**
     * 记录H5用户登录信息（IP、时间、设备）
     */
    private void recordLoginInfo(Long userId) {
        H5User user = new H5User();
        user.setUserId(userId);
        user.setLoginIp(IpUtils.getIpAddr());
        user.setLoginDate(DateUtils.getNowLocalDateTime());
        // 获取登录设备信息（如浏览器/手机型号）
        user.setLastLoginDevice(IpUtils.getHostIp());
        h5UserService.updateH5User(user);
    }

    /**
     * 检查用户是否通过手机号存在
     */
    public boolean checkUserExistsByPhone(String phoneNumber) {
        if (StringUtils.isEmpty(phoneNumber)) {
            return false;
        }
        H5User user = h5UserService.selectUserByPhoneNumber(phoneNumber);
        return user != null;
    }

    /**
     * 忘记密码重置
     */
    public void forgotPassword(String phoneNumber, String smsCode, String newPassword, String confirmPassword) {
        // 1. 参数校验
        if (StringUtils.isEmpty(phoneNumber)) {
            throw new ServiceException("手机号不能为空");
        }
        if (StringUtils.isEmpty(smsCode)) {
            throw new ServiceException("验证码不能为空");
        }
        if (StringUtils.isEmpty(newPassword)) {
            throw new ServiceException("新密码不能为空");
        }
        if (StringUtils.isEmpty(confirmPassword)) {
            throw new ServiceException("确认密码不能为空");
        }

        // 2. 密码一致性校验
        if (!newPassword.equals(confirmPassword)) {
            throw new ServiceException("两次输入的密码不一致");
        }

        // 3. 密码强度校验
        if (newPassword.length() < UserConstants.PASSWORD_MIN_LENGTH ||
                newPassword.length() > UserConstants.PASSWORD_MAX_LENGTH) {
            throw new ServiceException("密码长度必须在" + UserConstants.PASSWORD_MIN_LENGTH + "-"
                    + UserConstants.PASSWORD_MAX_LENGTH + "位之间");
        }

        // 4. 检查用户是否存在
        H5User user = h5UserService.selectUserByPhoneNumber(phoneNumber);
        if (user == null) {
            throw new ServiceException("该手机号未注册，请先注册");
        }

        // 5. 验证短信验证码
        if (!smsCodeService.verifySmsCode(phoneNumber, smsCode, "forgot_password")) {
            throw new ServiceException("验证码错误或已过期");
        }

        // 6. 加密新密码
        String encryptedPassword = SecurityUtils.encryptPassword(newPassword);

        // 7. 更新用户密码
        H5User updateUser = new H5User();
        updateUser.setUserId(user.getUserId());
        updateUser.setPassword(encryptedPassword);
        updateUser.setPwdUpdateDate(DateUtils.getNowLocalDateTime());
        updateUser.setUpdateTime(DateUtils.getNowLocalDateTime());

        int result = h5UserService.updateH5User(updateUser);
        if (result <= 0) {
            throw new ServiceException("密码重置失败，请稍后重试");
        }

        // 8. 记录操作日志
        AsyncManager.me().execute(AsyncFactory.recordLogininfor(phoneNumber, Constants.LOGIN_SUCCESS,
                "H5用户通过短信验证码重置密码成功"));
    }
}